// CDN_WEB_MSG_Http.h
//
// Copyright (c) 2011 LRUnit, LLC All Rights Reserved.
// Author : Kim McKinley
//
// This product includes software developed by :
//  LRUnit, LLC
//
// This software is under the terms of the MIT License :
/*
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/

#if !defined(CDN_WEB_MSG_Http_h)
#define CDN_WEB_MSG_Http_h

/// Character encoding codes that may be in a URL
//  Table used to lookup the There may be more but these are the popular ones. Reading RFC 1738
//   is like a who's who of internet protocols - these are the ones that seem the most common
const char *URL_CODES[] =
  { "20"," " , "3C","<" , "3E",">" , "23","#" , "25","%" , "7B","{" , "7D","}" , "7C","|" , "5C","\"" , 
    "5E","^" , "7E","~" , "5B","[" , "5D","]" , "60","`" , "3B",";" , "2F","/" , "3F","?" , "3A",":" ,
    "40","@" , "3D","=" , "26","&" , "24","$" };

/// Internet standard time formatted day
const char *DAY_NAMES[] =
  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
/// Internet standard time formatted month
const char *MONTH_NAMES[] =
  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

namespace cdn {
  namespace web {
    namespace msg {

// **********************************************************************
/// Parses HTTP 1.1 GET, HEAD, and POST messages and builds corresponding reply
/**
  This class contains both the request from the brower and your response to
  that request. The HTTP requests are parsed into, and replies constructed from,
  STL strings and maps that are contained in this class.

  The request is in 'http.req', the response you are building is in 'http.resp'.
  @verbatim
  For Example:
    To get the host that sent a request you would :
      std::string thatsillyhost = http.req.headers_["Host"];

    To add a 'Content-Type: text/plain' to the response header, do something like :
      http.resp.headers_["Content-Type"] = "text/plain";
    That header will be included in your reply.

    See some of the server extensions to give an idea how it is done.

    Note, since you are the boss, if you add an invalid header it will be sent!
     when you got a few extra days of nothing better to do - 
     read http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for a exhaustive
     list of valid HTTP headers.

    The HTTP header fields 'Server', 'Date', and 'Content-Length' will be
     automatically sent in replies. You do not have to worry about them. If you
     do not include a Content-Type a type of text/plain will be sent.

    Finally, if the HTTP response code returned from your extension is not 200 (OK) - 
     for example 403 (Forbidden), you do not have to provide any additional header
     information. A generic response will be generated and sent for that error.
  @endverbatim
*/
class Http
{
public:
  Http() : theDefaultContentType("text/plain") {}

  struct {
  // Containers that hold the info parsed from a request
  std::string method_;                              ///< GET, HEAD, or POST
  std::string path_;                                ///< Path from the request
  std::map< std::string, std::string > headers_;    ///< HTTP headers
  std::map< std::string, std::string > params_;     ///< Parameters
  } req;

  struct {
  // Containers that hold the info to build the associated response
  std::string code_;                                ///< HTTP Response code 200 = OK, etc
  std::stringstream content_;                       ///< Reply message content
  std::map< std::string, std::string > headers_;    ///< HTTP headers
  } rsp;

  const std::string theEmptyString;                 ///< reference to empty string
  const std::string theDefaultContentType;          ///< reference default content type

// **********************************************************************
  // Getters and Setters for commonly used HTTP message data
  /// Get the request method
  const std::string& method() const { return req.method_; }
  /// Set the request method
  std::string& method(const std::string &value) { req.method_ = value; return req.method_; }

  /// Get the request path
  const std::string& path() const { return req.path_; }
  /// Set the request path
  std::string& path(const std::string &value) { req.path_ = value; return req.path_; }

  /// Get the response HTTP code
  const std::string& code() const { return rsp.code_; }
  /// Set the response HTTP code
  std::string& code(const std::string &value) { rsp.code_ = value; return rsp.code_; }

  /// Get the response content stream
  std::stringstream& content() { return rsp.content_; }

  /// Get a request header value - returns an empty string if not found
  const std::string& requestHeader(const std::string &what)
  {
    if ( req.headers_.find(what) != req.headers_.end() )
      return req.headers_.find(what)->second;
    else
      return theEmptyString;
  }

  /// Set a request header value
  const std::string& requestHeader(const std::string &what, const std::string &value)
  {
    req.headers_[what] = value;
    return value;
  }

  /// Get a response header value - returns an empty string if not found
  const std::string& responseHeader(const std::string &what)
  {
    if ( rsp.headers_.find(what) != rsp.headers_.end() )
      return rsp.headers_.find(what)->second;
    else
      return theEmptyString;
  }

  /// Set a response header value
  const std::string& responseHeader(const std::string &what, const std::string &value)
  {
    rsp.headers_[what] = value;
    return value;
  }

  /// Get the response content type is used so often - this function does it!
  const std::string& contentType()
  {
    if ( responseHeader("Content-Type") == "" )
      return theDefaultContentType;
    else
      return responseHeader("Content-Type");
  }

  /// Setting the response content type is used so often - is handy shortcut
  const std::string& contentType(const std::string &value)
  {
    return responseHeader("Content-Type", value);
  }

// **********************************************************************
/// Extract the Method, Path, Headers and Parameters from a HTTP request
bool ParseHttpMessage(char *buf)
{
  int path_beg;

  // Copy the request and its params into a char buffer so we can parse value pairs easier
  //  Only going to grab a max of 8K chars - should be adequate for most HTTP headers and
  //  Url parameters
  char *pline, *pkey, *pvalue, *next_token;

  // See what type of request this is GET, POST, or HEAD
  if ( memcmp(buf, "POST ", 5) == 0 ) { req.method_ = "POST";  path_beg = 5; }
  else if ( memcmp(buf, "GET ", 4) == 0 ) { req.method_ = "GET ";  path_beg = 4; }
  else if ( memcmp(buf, "HEAD ", 5) == 0 ) { req.method_ = "HEAD";  path_beg = 5; }
  else return false;// we can not parse this request

  // Extract the path by looking for the 'HTTP' in the request url
  char *http_pos;
  if ( (http_pos = strstr(buf, "HTTP")) != NULL )
  {
    *(http_pos-1) = 0;
    req.path_ = buf + path_beg; // Might include the parameters - will check later
  }
  else  // No 'HTTP' in the request line? that is just naughty - is an invalid request
    return false;

  // Find the beginning of the header lines
  next_token = http_pos;
  while( *(next_token++) != '\n');

  // Extract the HTTP header pairs  Date: today\r\nPing: pong\r\nWhat: ever\r\n...
  // Extract each header line into a vector
  std::vector <char *> pairs;
  // The headers stopper is a cr/lf - ( blank line )
  while ( *next_token != '\r' && *(next_token+1) != '\n' &&
    (pline = strtok_s( NULL, "\r", &next_token)) != NULL )
  {
    pairs.push_back(pline);
    next_token++; // skip over the '\n' that is after the '\r'
  }

  // Break out the key/value to the request headers map
  while (!pairs.empty())
  {
    pkey = strtok_s( pairs.back(), ":", &next_token);
    while ( isspace(*next_token) ) next_token++;
    pvalue = strtok_s( NULL, "\t", &next_token);
    pairs.pop_back();
    if (pkey && pvalue) req.headers_[pkey] = pvalue;
  }

  // Now we'll extract the parameters of the request

  // Set next_token to the beginning of the parameter pairs
  // If a POST then the parameters are in the content body
  if ( req.method_ == "POST" )
  {
    char *csr; // Cursor to find the request's content
    for( csr = buf; *csr ; csr++ )
      // Find the end of the HTTP headers
      if ( *csr == '\r' && *(csr+1) == '\n' && *(csr+2) == '\r' && *(csr+3) == '\n')
      {
        next_token = csr+4; // Here is where the parameters begin
        break;
      }

    // POST with no data - give the path back to the server
    if ( *csr == 0 ) return true;
  }
  else // Is a GET or HEAD so the parameters are in the url line
  {
    // Do we have any value pairs in the GET/HEAD request
    if ( (next_token = strchr(buf,'?')) != NULL )
    { // yep
      *next_token++ = 0;
      req.path_ = buf+path_beg;
    }
    else
      return true; // No parameters - well got eveything else so we are good
  }

  // Extract the parameter pairs  this=that&ping=pong&what=ever&...
  //  rsing the same trick we did with the HTTP headers 
  // Put the key with the value in a vector
  pairs.clear();
  while ( (pkey = strtok_s( NULL, "&", &next_token)) != NULL)
    pairs.push_back(pkey);

  // Now break out the key/value to the request param map
  while (!pairs.empty())
  {
    pkey = strtok_s( pairs.back(), "=", &next_token);
    pvalue = strtok_s( NULL, "=", &next_token);
    pairs.pop_back();
    if (pkey && pvalue)
    {
      req.params_[pkey] = pvalue;
      ConvertUrlToAscii(req.params_.find(pkey)->second);
    }
  }

  // Sweet! - this bad boy is parsed
  return true;
}


// **********************************************************************
/// Convert from URL encoded characters to ascii
//  Note that the given std::string is returned modified.
void ConvertUrlToAscii(std::string &url)
{
  for ( size_t p=0; url.size()>2 && p<(url.size()-2); p++ )
  {
    if ( url[p] != '%' ) continue; // Look for the 'encoded' prefix

    // Scan Url codes table looking for the code
    for (int c=0; c < sizeof(URL_CODES); c+=2)
    {
      if ( *(URL_CODES[c]) == url[p+1] && *(URL_CODES[c]+1) == url[p+2] )
      {
        // Replace the code with the ascii character
        url.replace(p,3,URL_CODES[c+1],1);
        break;
      }
    }
  }
}

// **********************************************************************
// Internet standard time format helper
// Kudos to Cheeso at 
//   http://stackoverflow.com/questions/2726975/how-can-i-generate-an-rfc1123-date-string-from-c-code-win32

/// Internet standard time formatted GMT day/time
// Added the time parameter so that it can generate any date/time and gleaned it out for our use - kim
std::string Rfc1123_DateTime(time_t t = NULL)
{
  // Buffer to hold the date/time string
  char buf[30];

  // Get the current time unless a time was given
  if ( t == NULL  )
    time(&t);

  // Get the GMT time
  struct tm tm;
  gmtime_s(&tm, &t);

  // Format this bad boy to be rfc1123 compliant
  strftime(buf, 30, "---, %d --- %Y %H:%M:%S GMT", &tm);
  memcpy(buf, DAY_NAMES[tm.tm_wday], 3);
  memcpy(buf+8, MONTH_NAMES[tm.tm_mon], 3);

  return std::string(buf);
}


}; // class Http


} // namespace msg
} // namespace web
} // namespace cdn

#endif
